package cn.xiaolang.thread.concurrent;

import java.util.concurrent.TimeUnit;

/**
 * 单生产多消费(synchronized同步代码块解决并发资源共享问题)
 *
 * @author fuhw/Dean
 * @date 2020-03-06
 */
public class ProduceConsumerOneToN {

    public static void main(String[] args) {

//        TicketWithSyncLock ticketWithSyncLock = new TicketWithSyncLock(100);
        TicketNotSafe ticketNotSafe = new TicketNotSafe(100);
        new Thread(ticketNotSafe, "窗口1：").start();
        new Thread(ticketNotSafe, "窗口2：").start();
        new Thread(ticketNotSafe, "窗口3：").start();
    }
}


/**
 * <p>结果出现相同车票的原因：</p>
 * 一个线程在没有全部执行完任务代码后，就被CPU切换到其它的线程上，造成了数据的不安全性（线程安全问题）
 * <p>
 * <p>解决方案：</p>
 * 不能控制CPU，那就只能保证某个线程在操作完共享数据之后，其它线程才可以继续操作共享数据
 * <pre>{@code
 * synchronized(任意对象){
 *   //线程要操作的共享数据代码
 * }
 * }
 * </pre>
 */
class TicketNotSafe implements Runnable {

    //初始化车票数量
    private int tickNum;
    private boolean isRun = true;

    TicketNotSafe(int tickNum) {
        this.tickNum = tickNum;
    }

    @Override
    public void run() {
        while (isRun) {
            if (tickNum > 0) {
                System.err.println(Thread.currentThread().getName() + "出售了编号：" + tickNum + "的车票");
                tickNum--;
                if (tickNum <= 0) {
                    isRun = false;
                }
                try {
                    // 避免线程执行太快，看不到效果
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}

/**
 * synchronized同步代码块只能添加到while循环内部 (如果添加到while循环外部，就变成单线程代码了）
 * <p>在开发中，同步代码块是添加在任务代码中操作共享数据的位置上,对象锁也必须是同一个对象锁</p>
 * <p>
 * 同步代码块想要和非静态同步方法保持共享数据一致: synchronized（this）;
 * 同步代码块想要和静态同步方法使用同一个对象锁: synchronized(类名.class);
 * </p>
 */
class TicketWithSyncLock implements Runnable {
    //初始化车票数量
    private int tickNum;
    private boolean isRun = true;
    private final Object lockObj = new Object();

    public TicketWithSyncLock(int tickNum) {
        this.tickNum = tickNum;
    }

    @Override
    public void run() {
        // 执行run方法，一直处于卖票状态
        while (isRun) {
            synchronized (lockObj) {
                if (tickNum > 0) {
                    System.err.println(Thread.currentThread().getName() + "出售了编号：" + tickNum + "的车票");
                    tickNum--;
                    if (tickNum <= 0) {
                        isRun = false;
                    }
                    try {
                        // 避免线程执行太快，看不到效果
                        TimeUnit.MILLISECONDS.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }
}
